package com.test.chatrobot3.service;

import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import com.test.chatrobot3.entity.DialogueSample;
import org.deeplearning4j.datasets.iterator.impl.ListDataSetIterator;
import org.deeplearning4j.nn.conf.MultiLayerConfiguration;
import org.deeplearning4j.nn.conf.NeuralNetConfiguration;
import org.deeplearning4j.nn.conf.layers.DenseLayer;
import org.deeplearning4j.nn.conf.layers.GravesLSTM;
import org.deeplearning4j.nn.conf.layers.OutputLayer;
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
import org.deeplearning4j.util.ModelSerializer;
import org.nd4j.common.io.ClassPathResource;
import org.nd4j.linalg.activations.Activation;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.DataSet;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.linalg.learning.config.Adam;
import org.nd4j.linalg.lossfunctions.LossFunctions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service

public class ChatService {
private static final Logger logger = LoggerFactory.getLogger(ChatService.class);

    // 配置参数
    @Value("${chat.model.file:chatbot-model.zip}")
    private String modelFile;

    @Value("${chat.dataset.path:dataset.csv}")
    private String datasetPath;

    @Value("${chat.context.window:3}")
    private int contextWindowSize;

    @Value("${chat.vocab.file:vocabulary.txt}")
    private String vocabFile;//词汇表

    @Value("${chat.labels.file:labels.txt}")
    private String labelsFile;//标签


    @Autowired
    private DeepSeekService deepSeekService;

    // 核心组件
    private MultiLayerNetwork model;

    private String same;

    private List<String> vocabulary = new ArrayList<>();

    //存储标签到索引的映射
    private Map<String, Integer> labelIndex = new HashMap<>();

    //存储索引到标签的映射：
    private Map<Integer, String> indexLabel = new HashMap<>();

    // 会话上下文存储
    private Map<String, Deque<String>> sessionContexts = new ConcurrentHashMap<>();

    @PostConstruct
    public void initialize(){
        logger.info("初始化 ChatService...");
        try {
            File modelFile = new File(this.modelFile);
            if (modelFile.exists()) {
                logger.info("加载现有模型：{}", modelFile.getAbsolutePath());
                loadExistingModel(modelFile);
            } else {
                logger.info("训练新模型并保存到：{}", modelFile.getAbsolutePath());
                trainNewModel();
                saveModel(modelFile);
            }
            logger.info("词汇表大小：{}", vocabulary.size());
        } catch (Exception e) {
            logger.error("初始化 ChatService 失败", e);
            throw new RuntimeException("ChatService 初始化失败", e);
        }
    }

    //====================== 核心方法 ======================

    public String processMessage(String sessionId, String message) {
        try {
            // 重置LSTM隐藏状态
            model.rnnClearPreviousState();
            // 更新上下文
            updateSessionContext(sessionId, message);
            System.out.println(message+"==>message");
            // 构建 3D 特征向量 [1, vocabularySize, 1]
            List<String> contextWords = getContextFeatures(sessionId);
            System.out.println(sessionId+"===>sessionId");
            INDArray features = buildFeatureVector(contextWords);

            // 预测并返回结果
            INDArray output = model.output(features);
            int predictedIndex = Nd4j.argMax(output, 1).getInt(0);

            // 获取预测概率，并设置阈值
            String localAnswer = indexLabel.getOrDefault(predictedIndex,null);
            char c=message.charAt(message.length()-1);
            String s = Character.toString(c);
            System.out.println(s);

            if (s.equals("*")||localAnswer == null || localAnswer.equals("请换个说法再试一次")) {
                logger.info("本地模型无法回答，调用 DeepSeek API");
                same=null;
                System.out.println(message);
                message=message.substring(0,message.length()-1);
                System.out.println(message);
                return deepSeekService.getAnswerFromDeepSeek(message);
            }
            same=localAnswer;
            return localAnswer;
        } catch (Exception e) {
            logger.error("处理消息失败", e);
            return "服务暂时不可用，请稍后再试";
        }
    }

    private void trainNewModel() throws Exception {
        List<DialogueSample> samples = loadDataset();
        buildVocabulary(samples);
        buildLabelIndex(samples);
        MultiLayerConfiguration config = new NeuralNetConfiguration.Builder()
                .seed(1234)
                .updater(new Adam(0.001))
                .list()
                .layer(new GravesLSTM.Builder()
                                        .nIn(vocabulary.size())
                                        .nOut(50)
                                        .activation(Activation.TANH)
                                        .build())
                .layer(new DenseLayer.Builder()
                        .nIn(50)
                        .nOut(30)
                        .activation(Activation.RELU)
                        .build())
                .layer(new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
                                .nIn(30)
                                .nOut(labelIndex.size())
                                .activation(Activation.SOFTMAX)
                                .build()).build();
        model = new MultiLayerNetwork(config);
        model.init();
        ListDataSetIterator<DataSet> iterator = createTrainingDataSet(samples);
        model.fit(iterator, 200);
    }
    private void loadExistingModel(File modelFile) throws IOException {
        model = ModelSerializer.restoreMultiLayerNetwork(modelFile);
        loadVocabularyAndLabels();
    }

    private void saveModel(File modelFile) throws IOException {
        ModelSerializer.writeModel(model, modelFile, true);
        saveVocabularyAndLabels();
    }

    //====================== 数据处理 ======================
    private List<DialogueSample> loadDataset() throws IOException {
        //准备数组
        List<DialogueSample> samples = new ArrayList<>();
        //历史记录
        Map<String, List<String>> sessionHistory = new HashMap<>();
        try (
                //输入流
                InputStream is = new ClassPathResource(datasetPath).getInputStream();
             //缓存读数据
             BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {

            String line;
            br.readLine(); // 跳过开头
            //读到空行是结束
            while ((line = br.readLine()) != null) {
                //已,作为分割符

                String[] cols = line.split(",", 4);
                //若存储数组的长度小于四则表示分割的数据错误，数据不做处理
                if (cols.length < 4) continue;
                //去掉字符串首尾空格。
                String sessionId = cols[0].trim();
                int turn = Integer.parseInt(cols[1].trim());
                //去掉字符串首尾空格，且去掉用户：
                String userMessage = processText(cols[2].trim().replace("用户：", ""));
                //去掉字符串首尾空格，且去掉机器人：
                String botResponse = cols[3].trim().replace("机器人：", "");
                //建立一个数据体
                DialogueSample sample = new DialogueSample(sessionId, turn, userMessage,botResponse);
                // 构建上下文特征
                //从session拿出上文特征，如果sessionId存在则取出对应的value值，不存在则一个为空的新的。
                List<String> context = sessionHistory.getOrDefault(sessionId, new ArrayList<>());
                //得到上下文特征
                List<String> currentFeatures = extractFeatures(userMessage, context);
                //存储在用户中
                sample.setContextFeatures(currentFeatures);

                samples.add(sample);

                sessionHistory.put(sessionId, currentFeatures);
            }
        }
        return samples;
    }

    private List<String> extractFeatures(String text, List<String> previousContext) {

//        // 1. 中文分词和特征提取
//        //特征
//        List<String> features = new ArrayList<>();
//        //分词 使用用户提出的问题
//        List<Term> terms = HanLP.segment(text);
//
//        // 提取名词和动词作为特征
//        features.addAll(terms.stream()
//                .filter(term -> term.nature.toString().startsWith("n") ||
//                        term.nature.toString().startsWith("v"))
//                .map(term -> term.word)
//                .collect(Collectors.toList()));
//
//        // 添加上下文特征
//        features.addAll(previousContext.stream()
//                .limit(contextWindowSize)
//                .collect(Collectors.toList()));
        //中文粉刺
        List<Term> terms = HanLP.segment(text);
        List<String> features = terms.stream()
                .filter(term -> {
                    String nature = term.nature.toString();
                    return nature.startsWith("n") || // 名词
                            nature.startsWith("v") || // 动词
                            nature.startsWith("kw");  // 自定义关键词
                })
                .map(term -> term.word)
                .collect(Collectors.toList());

        return features;
    }

    //====================== 工具方法 ======================
    private void buildVocabulary(List<DialogueSample> samples) {
        //创建一个HashSet，用于临时存储唯一的词汇（自动去重）
        Set<String> vocabSet = new HashSet<>();
        //遍历
        samples.forEach(sample -> {
            //sample.contextFeatures()
            //将当前样本的上下文特征（关键词列表）添加到词汇集合
            vocabSet.addAll(sample.getContextFeatures());

            //提取机器人回复中的关键词，并添加到词汇集合
            //Collections.emptyList()：返回一个空的、不可变的List对象
            //    - extractFeatures()方法通过分词提取关键词
            //    - Collections.emptyList()表示无历史上下文（独立处理回复）
            vocabSet.addAll(extractFeatures(sample.getBotResponse(), Collections.emptyList()));
        });

        //将HashSet转换为ArrayList，便于后续按索引访问
        vocabulary = new ArrayList<>(vocabSet);

        // 6. 对词汇表按字母顺序排序，确保特征向量的一致性
        Collections.sort(vocabulary);

        logger.info("词汇表大小：{}", vocabulary.size());
    }

    //构建机器人回复（标签）到索引的双向映射表（labelIndex 和 indexLabel）。
    private void buildLabelIndex(List<DialogueSample> samples) {
        //1. 提取所有机器人回复（标签）
        Set<String> labels = samples.stream()
                .map(DialogueSample::getBotResponse)//获取机器回复
                .collect(Collectors.toSet());//去重
        // // 2. 为每个标签分配唯一索引
        int index = 0;
        for (String label : labels) {
            // 3. 将标签和索引存储到映射表中
            labelIndex.put(label, index); // 标签 -> 索引
            indexLabel.put(index, label);// 索引 -> 标签
            index++;
        }
        // 添加默认的“无法回答”标识
        indexLabel.put(-1, "请换个说法再试一次");
    }

    /**
     * 上下文特征：可以使得对话具有连续性，可以形成一轮连续性对话，
     * 可以问题提问，和机器人回答的相关性。
     * 存储用户问题的关键信息，和存储session
     *
     * 数值向量：是将用户输入和上下文特征转化为神经网络可以处理的数值向量。
     *
     * @param features
     * @return
     */
    //将用户输入或上下文特征转换为神经网络可处理的数值向量（特征向量）。
    private INDArray buildFeatureVector(List<String> features) {
//        INDArray vector = Nd4j.zeros(1, vocabulary.size());
//        features.forEach(feature -> {
//            int index = vocabulary.indexOf(feature);
//            if (index >= 0) {
//                vector.putScalar(index, 1.0);
//            }
//        });
//        return vector;
        // 创建 2D 向量 [1, vocabularySize]
        //创造一个一个大小为 [1, vocabularySize] 的零向量
        INDArray vector = Nd4j.zeros(1, vocabulary.size());

        //遍历输入的关键词列表，将每个关键词在词汇表中的索引位置设为 1
        features.forEach(feature -> {
            // 获取关键词在词汇表中的索引
            int index = vocabulary.indexOf(feature);
            //如果关键词在词汇表中，将对应位置设为 1
            if (index >= 0) {
                vector.putScalar(0, index, 1.0); // 修改为 2D 索引
            }
        });

        // 将 2D 向量转换为 3D 向量 [1, vocabularySize, 1]
        return vector.reshape(1, vocabulary.size(), 1);
    }

    //====================== 上下文管理 ======================
    //更新会话上下文，将用户输入的关键词添加到当前会话的上下文窗口中。
    private void updateSessionContext(String sessionId, String message) {
        //获取当前会话的上下文队列（如果不存在则创建）
        Deque<String> context = sessionContexts.computeIfAbsent(sessionId, k -> new ArrayDeque<>());

        // 添加新特征并维护窗口大小，提取用户输入的关键词
        List<String> newFeatures = extractFeatures(message, new ArrayList<>(context));

        // 清空旧上下文（关键修改！）
        context.clear();


        newFeatures.forEach(feature -> {
            //3. 将关键词添加到上下文队列中
            context.addLast(feature);
            //4. 如果队列大小超过窗口限制，移除最早的关键词
            if (context.size() > contextWindowSize * 2) {
                context.removeFirst();
            }
        });
    }

    private List<String> getContextFeatures(String sessionId) {
        return new ArrayList<>(sessionContexts.getOrDefault(sessionId, new ArrayDeque<>()));
    }

    private ListDataSetIterator<DataSet> createTrainingDataSet(List<DialogueSample> samples) {
        //1.初始化训练数据列表
        List<DataSet> trainingData = new ArrayList<>();

        //遍历
        for (DialogueSample sample : samples) {
            // 1. 构建特征向量
            INDArray features = buildFeatureVector(sample.getContextFeatures());

            // 2. 构建机器回复的索引
            int labelIdx = labelIndex.get(sample.getBotResponse());

            // 构建标签向量 [1, labelIndex.size()]
            INDArray labels = Nd4j.zeros(1, labelIndex.size());
            labels.putScalar(0,labelIdx, 1.0);//将对应索引设为一

            // 3. 添加到训练数据，将特征向量和标签向量组组合为DataSet对象
            trainingData.add(new DataSet(features, labels));
        }

        // 4. 创建 DataSetIterator用于训练模型
        return new ListDataSetIterator<>(trainingData, 8); // 批量大小为8
    }


    //====================== 辅助方法 ======================
    private String processText(String text) {
        return text.replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9]", "");
    }

    private void loadVocabularyAndLabels() throws IOException {
        // 实现词汇表和标签的加载逻辑
//        File vocabFile = new File(this.vocabFile);
//        File labelsFile = new File(this.labelsFile);
        File vocabFile = new ClassPathResource("data/vocab.txt").getFile();
        File labelsFile = new ClassPathResource("data/labels.txt").getFile();

        // 1. 加载词汇表
        vocabulary = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(vocabFile))) {
            String line;
            while ((line = reader.readLine()) != null) {
                vocabulary.add(line.trim());
            }
        }

        // 2. 加载标签索引
        labelIndex = new HashMap<>();
        indexLabel = new HashMap<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(labelsFile))) {
            String line;
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split("=");
                if (parts.length == 2) {
                    String label = parts[0].trim();
                    int index = Integer.parseInt(parts[1].trim());
                    labelIndex.put(label, index);
                    indexLabel.put(index, label);
                }
            }
        }
    }

    private void saveVocabularyAndLabels() throws IOException {
        // 实现词汇表和标签的保存逻辑
//        File vocabFile = new File(this.vocabFile);
//        File labelsFile = new File(this.labelsFile);
        File vocabFile = new ClassPathResource("data/vocab.txt").getFile();
        File labelsFile = new ClassPathResource("data/labels.txt").getFile();
        // 1. 保存词汇表
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(vocabFile))) {
            for (String word : vocabulary) {
                writer.write(word);
                writer.newLine();
            }
        }
        // 2. 保存标签索引
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(labelsFile))) {
            for (Map.Entry<String, Integer> entry : labelIndex.entrySet()) {
                writer.write(entry.getKey() + "=" + entry.getValue());
                writer.newLine();
            }
        }
    }

    public void resetSessionContext(String sessionId) {
        sessionContexts.remove(sessionId);
    }

}